 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.menus;

 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.core.expressions.Expression;
 import org.eclipse.core.expressions.ExpressionConverter;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.InvalidRegistryObjectException;
 import org.eclipse.jface.action.GroupMarker;
 import org.eclipse.jface.action.IContributionItem;
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.action.ToolBarContributionItem;
 import org.eclipse.jface.action.ToolBarManager;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.ui.IWorkbenchWindow;
 import org.eclipse.ui.actions.CompoundContributionItem;
 import org.eclipse.ui.internal.WorkbenchWindow;
 import org.eclipse.ui.internal.provisional.presentations.IActionBarPresentationFactory;
 import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
 import org.eclipse.ui.internal.util.Util;
 import org.eclipse.ui.menus.AbstractContributionFactory;
 import org.eclipse.ui.menus.CommandContributionItem;
 import org.eclipse.ui.menus.IContributionRoot;
 import org.eclipse.ui.menus.IMenuService;
 import org.eclipse.ui.menus.WorkbenchWindowControlContribution;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.eclipse.ui.services.IServiceLocator;

 /**
  * @since 3.3
  *
  */
 public class MenuAdditionCacheEntry extends AbstractContributionFactory {
     private IConfigurationElement additionElement;

     // Caches

     /**
      * Maps an IContributionItem to its corresponding IConfigurationElement
      */
     Map iciToConfigElementMap = new HashMap ();

     /**
      * If an {@link IConfigurationElement} is in the Set then we have already
      * tried (and failed) to load the associated ExecutableExtension.
      *
      * This is used to prevent multiple retries which would spam the Log.
      */
     Set failedLoads = new HashSet ();

     /**
      * Maps an IConfigurationElement to its parsed Expression
      */
     private HashMap visWhenMap = new HashMap ();

     /**
      * The menu service on which to generate all subcaches.
      */
     private IMenuService menuService;

     public MenuAdditionCacheEntry(IMenuService menuService,
             IConfigurationElement element, String location, String namespace) {
         super(location, namespace);
         this.menuService = menuService;
         this.additionElement = element;
         generateSubCaches();
     }

     /**
      *
      */
     private void generateSubCaches() {
         IConfigurationElement[] items = additionElement.getChildren();
         for (int i = 0; i < items.length; i++) {
             String itemType = items[i].getName();
             if (IWorkbenchRegistryConstants.TAG_MENU.equals(itemType)
                     || IWorkbenchRegistryConstants.TAG_TOOLBAR.equals(itemType)) {
                 // Menus and toolbars are special...we have to add any sub menu
 // items into their own cache
 // If the locationURI is null then this should be a sub menu
 // addition..create the 'root' URI

                 String location = new MenuLocationURI(getLocation())
                         .getScheme()
                         + ":" + MenuAdditionCacheEntry.getId(items[i]); //$NON-NLS-1$

                 // -ALL- contibuted menus must have an id so create one
 // if necessary
 MenuAdditionCacheEntry subMenuEntry = new MenuAdditionCacheEntry(
                         menuService, items[i], location, getNamespace());
                 menuService.addContributionFactory(subMenuEntry);
             }
         }
     }

     private Expression getVisibleWhenForItem(IContributionItem item) {
         IConfigurationElement configElement = (IConfigurationElement) iciToConfigElementMap
                 .get(item);
         if (configElement == null)
             return null;

         if (!visWhenMap.containsKey(configElement)) {
             // Not parsed yet
 try {
                 IConfigurationElement[] visibleConfig = configElement
                         .getChildren(IWorkbenchRegistryConstants.TAG_VISIBLE_WHEN);
                 if (visibleConfig.length > 0 && visibleConfig.length < 2) {
                     IConfigurationElement[] visibleChild = visibleConfig[0]
                             .getChildren();
                     if (visibleChild.length > 0) {
                         Expression visWhen = ExpressionConverter.getDefault()
                                 .perform(visibleChild[0]);
                         visWhenMap.put(configElement, visWhen);
                     }
                 }
             } catch (InvalidRegistryObjectException e) {
                 visWhenMap.put(configElement, null);
                 // TODO Auto-generated catch block
 e.printStackTrace();
             } catch (CoreException e) {
                 visWhenMap.put(configElement, null);
                 // TODO Auto-generated catch block
 e.printStackTrace();
             }
         }

         return (Expression) visWhenMap.get(configElement);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.menus.AbstractContributionFactory#createContributionItems(org.eclipse.ui.internal.menus.IMenuService,
      * java.util.List)
      */
     public void createContributionItems(IServiceLocator serviceLocator,
             IContributionRoot additions) {
         IActionBarPresentationFactory actionBarPresentationFactory = null;

         WorkbenchWindow window = (WorkbenchWindow) serviceLocator
                 .getService(IWorkbenchWindow.class);
         if (window != null) {
             actionBarPresentationFactory = window
                     .getActionBarPresentationFactory();
         }

         IConfigurationElement[] items = additionElement.getChildren();
         for (int i = 0; i < items.length; i++) {
             String itemType = items[i].getName();
             IContributionItem newItem = null;

             if (IWorkbenchRegistryConstants.TAG_COMMAND.equals(itemType)) {
                 newItem = createCommandAdditionContribution(serviceLocator,
                         items[i]);
             } else if (IWorkbenchRegistryConstants.TAG_DYNAMIC.equals(itemType)) {
                 newItem = createDynamicAdditionContribution(items[i]);
             } else if (IWorkbenchRegistryConstants.TAG_CONTROL.equals(itemType)) {
                 newItem = createControlAdditionContribution(items[i]);
             } else if (IWorkbenchRegistryConstants.TAG_SEPARATOR
                     .equals(itemType)) {
                 newItem = createSeparatorAdditionContribution(items[i]);
             } else if (IWorkbenchRegistryConstants.TAG_MENU.equals(itemType)) {
                 newItem = createMenuAdditionContribution(items[i]);
             } else if (IWorkbenchRegistryConstants.TAG_TOOLBAR.equals(itemType)) {
                 newItem = createToolBarAdditionContribution(
                         actionBarPresentationFactory, items[i]);
             }

             // Cache the relationship between the ICI and the
 // registry element used to back it
 if (newItem != null) {
                 iciToConfigElementMap.put(newItem, items[i]);
                 additions.addContributionItem(newItem,
                         getVisibleWhenForItem(newItem));
             }
         }
     }

     /**
      * @param configurationElement
      * @return
      */
     private IContributionItem createToolBarAdditionContribution(
             IActionBarPresentationFactory actionBarPresentationFactory,
             IConfigurationElement configurationElement) {
         if (!getLocation().startsWith("toolbar")) { //$NON-NLS-1$
 return null;
         }
         if (actionBarPresentationFactory != null) {
             return actionBarPresentationFactory.createToolBarContributionItem(
                     actionBarPresentationFactory.createToolBarManager(),
                     getId(configurationElement));
         }
         return new ToolBarContributionItem(new ToolBarManager(),
                 getId(configurationElement));
     }

     /**
      * @param configurationElement
      * @return the menu manager
      */
     private IContributionItem createMenuAdditionContribution(
             final IConfigurationElement menuAddition) {
         // Is this for a menu or a ToolBar ? We can't create
 // a menu directly under a Toolbar; we have to add an
 // item of style 'pulldown'
 if (getLocation().startsWith("toolbar")) { //$NON-NLS-1$
 return null;
         }

         String text = getLabel(menuAddition);
         String mnemonic = getMnemonic(menuAddition);
         if (text != null && mnemonic != null) {
             int idx = text.indexOf(mnemonic);
             if (idx != -1) {
                 text = text.substring(0, idx) + '&' + text.substring(idx);
             }
         }
         return new MenuManager(text, getId(menuAddition));
     }

     /**
      * @param configurationElement
      * @return
      */
     private IContributionItem createSeparatorAdditionContribution(
             final IConfigurationElement sepAddition) {
         if (isSeparatorVisible(sepAddition)) {
             return new Separator(getName(sepAddition));
         }
         return new GroupMarker(getName(sepAddition));
     }

     /**
      * @return
      */
     private IContributionItem createDynamicAdditionContribution(
             final IConfigurationElement dynamicAddition) {
         // If we've already tried (and failed) to load the
 // executable extension then skip this addition.
 if (failedLoads.contains(dynamicAddition))
             return null;

         // Attempt to load the addition's EE (creates a new instance)
 final CompoundContributionItem loadedDynamicContribution = (CompoundContributionItem) Util
                 .safeLoadExecutableExtension(dynamicAddition,
                         IWorkbenchRegistryConstants.ATT_CLASS,
                         CompoundContributionItem.class);

         // Cache failures
 if (loadedDynamicContribution == null) {
             failedLoads.add(loadedDynamicContribution);
             return null;
         }

         // TODO provide a proxy IContributionItem that defers instantiation
 // adding contribution items in a menu instantiates this object ...
 // we need to defer loading until fill(*) is called.
 return loadedDynamicContribution;
     }

     /**
      * @return
      */
     private IContributionItem createControlAdditionContribution(
             final IConfigurationElement widgetAddition) {
         if (!getLocation().startsWith("toolbar")) { //$NON-NLS-1$
 return null;
         }
         // If we've already tried (and failed) to load the
 // executable extension then skip this addirion.
 if (failedLoads.contains(widgetAddition))
             return null;

         // Attempt to load the addition's EE (creates a new instance)
 final WorkbenchWindowControlContribution loadedWidget = (WorkbenchWindowControlContribution) Util
                 .safeLoadExecutableExtension(widgetAddition,
                         IWorkbenchRegistryConstants.ATT_CLASS,
                         WorkbenchWindowControlContribution.class);

         // Cache failures
 if (loadedWidget == null) {
             failedLoads.add(widgetAddition);
             return null;
         }

         // explicitly set the id
 ((InternalControlContribution) loadedWidget)
                 .setId(getId(widgetAddition));

         return loadedWidget;
     }

     /**
      * @param configurationElement
      * @return
      */
     private IContributionItem createCommandAdditionContribution(
             IServiceLocator locator, final IConfigurationElement commandAddition) {
         return new CommandContributionItem(locator, getId(commandAddition),
                 getCommandId(commandAddition), getParameters(commandAddition),
                 getIconDescriptor(commandAddition),
                 getDisabledIconDescriptor(commandAddition),
                 getHoverIconDescriptor(commandAddition),
                 getLabel(commandAddition), getMnemonic(commandAddition),
                 getTooltip(commandAddition), getStyle(commandAddition));
     }

     /*
      * Support Utilities
      */
     public static String getId(IConfigurationElement element) {
         String id = element.getAttribute(IWorkbenchRegistryConstants.ATT_ID);

         // For sub-menu management -all- items must be id'd so enforce this
 // here (we could optimize by checking the 'name' of the config
 // element == "menu"
 if (id == null || id.length() == 0)
             id = element.toString();

         return id;
     }

     static String getName(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_NAME);
     }

     static String getLabel(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_LABEL);
     }

     static String getMnemonic(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_MNEMONIC);
     }

     static String getTooltip(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_TOOLTIP);
     }

     static String getIconPath(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_ICON);
     }

     static String getDisabledIconPath(IConfigurationElement element) {
         return element
                 .getAttribute(IWorkbenchRegistryConstants.ATT_DISABLEDICON);
     }

     static String getHoverIconPath(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_HOVERICON);
     }

     static ImageDescriptor getIconDescriptor(IConfigurationElement element) {
         String extendingPluginId = element.getDeclaringExtension()
                 .getContributor().getName();

         String iconPath = getIconPath(element);
         if (iconPath != null) {
             return AbstractUIPlugin.imageDescriptorFromPlugin(
                     extendingPluginId, iconPath);
         }
         return null;
     }

     static ImageDescriptor getDisabledIconDescriptor(
             IConfigurationElement element) {
         String extendingPluginId = element.getDeclaringExtension()
                 .getContributor().getName();

         String iconPath = getDisabledIconPath(element);
         if (iconPath != null) {
             return AbstractUIPlugin.imageDescriptorFromPlugin(
                     extendingPluginId, iconPath);
         }
         return null;
     }

     static ImageDescriptor getHoverIconDescriptor(IConfigurationElement element) {
         String extendingPluginId = element.getDeclaringExtension()
                 .getContributor().getName();

         String iconPath = getHoverIconPath(element);
         if (iconPath != null) {
             return AbstractUIPlugin.imageDescriptorFromPlugin(
                     extendingPluginId, iconPath);
         }
         return null;
     }

     public static boolean isSeparatorVisible(IConfigurationElement element) {
         String val = element
                 .getAttribute(IWorkbenchRegistryConstants.ATT_VISIBLE);
         return Boolean.valueOf(val).booleanValue();
     }

     public static String getClassSpec(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_CLASS);
     }

     public static String getCommandId(IConfigurationElement element) {
         return element.getAttribute(IWorkbenchRegistryConstants.ATT_COMMAND_ID);
     }

     private int getStyle(IConfigurationElement element) {
         String style = element
                 .getAttribute(IWorkbenchRegistryConstants.ATT_STYLE);
         if (style == null || style.length() == 0) {
             return CommandContributionItem.STYLE_PUSH;
         }
         if (IWorkbenchRegistryConstants.STYLE_TOGGLE.equals(style)) {
             return CommandContributionItem.STYLE_CHECK;
         }
         if (IWorkbenchRegistryConstants.STYLE_RADIO.equals(style)) {
             return CommandContributionItem.STYLE_RADIO;
         }
         if (IWorkbenchRegistryConstants.STYLE_PULLDOWN.equals(style)) {
             return CommandContributionItem.STYLE_PULLDOWN;
         }
         return CommandContributionItem.STYLE_PUSH;
     }

     /**
      * @param element
      * @return A map of parameters names to parameter values. All Strings. The
      * map may be empty.
      */
     public static Map getParameters(IConfigurationElement element) {
         HashMap map = new HashMap ();
         IConfigurationElement[] parameters = element
                 .getChildren(IWorkbenchRegistryConstants.TAG_PARAMETER);
         for (int i = 0; i < parameters.length; i++) {
             String name = parameters[i]
                     .getAttribute(IWorkbenchRegistryConstants.ATT_NAME);
             String value = parameters[i]
                     .getAttribute(IWorkbenchRegistryConstants.ATT_VALUE);
             if (name != null && value != null) {
                 map.put(name, value);
             }
         }
         return map;
     }
 }

